using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;

public class NetworkPlayerInfos
{
    public int playerNumber;
    public string playerName;
    public NetworkPlayer networkPlayer;

    public NetworkPlayerInfos(int index, string name, NetworkPlayer network)
    {
        playerNumber = index;
        playerName = name;
        networkPlayer = network;
    }
}

public enum ServerStatus
{
    Up,
    InGame,
    Down
}

//TODO : Gestion des pseudos identiques
public class Networking : MonoBehaviour
{
    public string gameTypeName = "Byblus";
    public string gameComment = "";
    public string serverIp = "127.0.0.1";
    public int serverPort = 25001;
    public int maxConnections = 2;

    //connections test fields
    public string testStatus = "Testing network capabilities";
    public string testMessage = "Test in progress";
    public string shouldEnableNatMessage = "";
    public bool doneTesting = false;
    public bool probingPublicIP = false;
    ConnectionTesterStatus connectionTestResult = ConnectionTesterStatus.Undetermined;
    // Indicates if the useNat parameter be enabled when starting a server
    bool useNat = false;

    //server fields
    public Dictionary<NetworkPlayer, string> connectedPlayers;
	public Dictionary<NetworkPlayer, bool> readyPlayers;
    public ServerStatus serverStatus;

    //client fields
    public Dictionary<string, NetworkPlayer> playersByName;
    public Dictionary<NetworkPlayer, string> playersByNetwork;
	

    //Error Window
    public bool showErrorWindow;
    private Rect errorWindow;
    private string windowTitle;
    private string windowMessage;
	
	//MasterServer Data
	public HostData[] hostData;

    public void Awake()
    {
        //init values
        serverStatus = ServerStatus.Down;
        playersByName = new Dictionary<string, NetworkPlayer>();
        playersByNetwork = new Dictionary<NetworkPlayer, string>();
		connectedPlayers = new Dictionary<NetworkPlayer, string>();
		readyPlayers = new Dictionary<NetworkPlayer, bool>();

        //TestConnection();
        //MasterServer.ClearHostList();
        MasterServer.RequestHostList("Byblus");
    }
	
	public void RefreshMasterServerList()
	{
		MasterServer.ClearHostList();
		MasterServer.RequestHostList("Byblus");
	}
		
	public void StartOnlineServer(string name, int port)
	{
	    serverPort = port;
		
		
	    //maxConnections = nbPlayers - 1; //because we count the server as a player
	    //gameComment = GameInfo.version;
	
	    //Set the game settings
	    this.GetComponent<GeneralManager>().gameName = name;
	
	    Network.InitializeServer(maxConnections, serverPort, !Network.HavePublicAddress());
	    MasterServer.RegisterHost("Byblus", name, "Description");
	}
	
    public void StartServer(string name, int port)
    {
        serverPort = port;
        //maxConnections = nbPlayers - 1; //because we count the server as a player
        //gameComment = GameInfo.version;

        //Set the game settings
        this.GetComponent<GeneralManager>().gameName = name;

        Network.InitializeServer(maxConnections, serverPort, useNat);
        //MasterServer.RegisterHost(gameTypeName, this.GetComponent<this.GetComponent<GeneralManager>()>().gameName, gameComment);
    }

    public void OnServerInitialized()
    {
        //server keep a list of current player
        connectedPlayers.Add(networkView.owner, this.GetComponent<GeneralManager>().playerName);
		readyPlayers.Add(networkView.owner, false);

        serverStatus = ServerStatus.Up;
        this.GetComponent<GeneralManager>().gameStatus = GameStatus.Lobby;

        //update local field because we are also a player
        AddPlayer(this.GetComponent<GeneralManager>().playerName, Network.player);
        networkView.RPC("NewPlayerToClient", RPCMode.OthersBuffered, this.GetComponent<GeneralManager>().playerName, Network.player);
    }

    public void OnPlayerConnected(NetworkPlayer player)
    {
        Debug.Log("Player connected from " + player.ipAddress + ":" + player.port);
    }

    public void OnPlayerDisconnected(NetworkPlayer player)
    {
        //notice all the clients that a player leave
        string leaverName = connectedPlayers[player];
		Network.RemoveRPCs(player);
        networkView.RPC("PlayerLeftToClient", RPCMode.All, leaverName, player);
		playersByName.Remove(leaverName);
        playersByNetwork.Remove(player);
		readyPlayers.Remove(player);
		connectedPlayers.Remove(player);
    }
	
	public void NewPlayerReady ( )
	{
		networkView.RPC("NewPlayerReadyToClient", RPCMode.All, Network.player);
	}
		
    public void TestConnection()
    {
        // Start/Poll the connection test, report the results in a label and
        // react to the results accordingly
        connectionTestResult = Network.TestConnection();
        switch (connectionTestResult)
        {
            case ConnectionTesterStatus.Error:
                testMessage = "Problem determining NAT capabilities";
                doneTesting = true;
                break;

            case ConnectionTesterStatus.Undetermined:
                testMessage = "Undetermined NAT capabilities";
                doneTesting = false;
                break;

            case ConnectionTesterStatus.PublicIPIsConnectable:
                testMessage = "Directly connectable public IP address.";
                useNat = false;
                doneTesting = true;
                break;

            // This case is a bit special as we now need to check if we can
            // circumvent the blocking by using NAT punchthrough
            case ConnectionTesterStatus.PublicIPPortBlocked:
                testMessage = "Non-connectble public IP address (port " +
                serverPort + " blocked), running a server is impossible.";
                useNat = false;
                // If no NAT punchthrough test has been performed on this public
                // IP, force a test
                float timer = 0;
                if (!probingPublicIP)
                {
                    connectionTestResult = Network.TestConnectionNAT();
                    probingPublicIP = true;
                    testStatus = "Testing if blocked public IP can be circumvented";
                    timer = Time.time + 10;
                }
                // NAT punchthrough test was performed but we still get blocked
                else if (Time.time > timer)
                {
                    probingPublicIP = false; // reset
                    useNat = true;
                    doneTesting = true;
                }
                break;

            case ConnectionTesterStatus.PublicIPNoServerStarted:
                testMessage = "Public IP address but server not initialized, " +
                "it must be started to check server accessibility. Restart " +
                "connection test when ready.";
                useNat = true;
                doneTesting = true;
                break;

            case ConnectionTesterStatus.LimitedNATPunchthroughPortRestricted:
                testMessage = "Limited NAT punchthrough capabilities. Cannot " +
                "connect to all types of NAT servers. Running a server " +
                "is ill advised as not everyone can connect.";
                useNat = true;
                doneTesting = true;
                break;

            case ConnectionTesterStatus.LimitedNATPunchthroughSymmetric:
                testMessage = "Limited NAT punchthrough capabilities. Cannot " +
                "connect to all types of NAT servers. Running a server " +
                "is ill advised as not everyone can connect.";
                useNat = true;
                doneTesting = true;
                break;

            case ConnectionTesterStatus.NATpunchthroughFullCone:
                testMessage = "NAT punchthrough capable. Can connect to all " +
                "servers and receive connections from all clients. Enabling " +
                "NAT punchthrough functionality.";
                useNat = true;
                doneTesting = true;
                break;

            case ConnectionTesterStatus.NATpunchthroughAddressRestrictedCone:
                testMessage = "NAT punchthrough capable. Can connect to all " +
                "servers and receive connections from all clients. Enabling " +
                "NAT punchthrough functionality.";
                useNat = true;
                doneTesting = true;
                break;

            default:
                testMessage = "Error in test routine, got " + connectionTestResult;
                break;
        }
        if (doneTesting)
        {
            if (useNat)
            {
                shouldEnableNatMessage = "When starting a server the NAT " +
                "punchthrough feature should be enabled (useNat parameter)";
            }
            else
            {
                shouldEnableNatMessage = "NAT punchthrough not needed";
            }
            testStatus = "TEST DONE";
        }
    }

    #region Client

    public void OnConnectedToServer()
    {
        //send player name to server
		networkView.RPC("NewPlayerToClient", RPCMode.All, this.GetComponent<GeneralManager>().playerName, Network.player);
    }

    public void OnDisconnectedFromServer(NetworkDisconnection info)
    {
        windowTitle = "Disconnected from the server";
        windowMessage = "You have been disconnected from the server";
        showErrorWindow = true;

        //clean game fields
        CleanServerVars();
        this.GetComponent<GeneralManager>().gameStatus = GameStatus.Start;
        serverStatus = ServerStatus.Down;
    }
	
	void CleanServerVars()
	{
		playersByName.Clear();
        playersByNetwork.Clear();
		readyPlayers.Clear();
		connectedPlayers.Clear();
	}
	
    public void DirectConnectToServer(string ip, int port)
    {
        serverIp = ip;
        serverPort = port;
        Network.Connect(serverIp, serverPort);
    }
	
	 public void DirectConnectToOnlineServer(string[] ip, int port)
    {
        Network.Connect(ip, port );
    }

    [RPC]
    public void NewPlayerToClient(string namePlayer, NetworkPlayer networkPlayer)
    {
        AddPlayer(namePlayer, networkPlayer);
		readyPlayers.Add(networkPlayer, false);
		connectedPlayers.Add(networkPlayer, namePlayer);
    }
	
	[RPC]
    public void NewPlayerReadyToClient(NetworkPlayer networkPlayer)
    {
		readyPlayers[networkPlayer] = true;
        // on regarde si il y a bien 2 joueurs et qu'ils sont ready
		bool ok = true;
		foreach (KeyValuePair<NetworkPlayer, bool> player in readyPlayers)
		{
        	if ( ! player.Value )
			{
				ok = false;
			}
		}
		if ( readyPlayers.Count == 2 && ok)
		{
			this.GetComponent<GeneralManager>().LaunchGame();
		}
    }
	
    [RPC]
    public void PlayerLeftToClient(string leaverName, NetworkPlayer leaverNetwork)
    {
        //remove the leaver from the local players lists
        RemovePlayer(leaverName, leaverNetwork);
        //TODO : message d'info
    }

    public void AddPlayer(string name, NetworkPlayer net)
    {
        //check if the player is already in the dictionnarys.
        //This is needed because the newplayer function is buffered by the server and if a same player leave en reco
        //he will have 2 times the RPC to add himself
        if (playersByName.ContainsKey(name))
        {
            playersByNetwork.Remove(playersByName[name]);
            playersByName.Remove(name);
        }
        playersByName.Add(name, net);
        playersByNetwork.Add(net, name);
    }

    public void RemovePlayer(string name, NetworkPlayer net)
    {
		Debug.Log ("Client is deconnected");
		
		Network.RemoveRPCs(net);
        Network.DestroyPlayerObjects(net);
		
        playersByName.Remove(name);
        playersByNetwork.Remove(net);
		readyPlayers.Remove(net);
		connectedPlayers.Remove(net);

		if ( this.GetComponent<GeneralManager>().gameStatus == GameStatus.Game ) 
		{
			// on coute le serveur et on deco l'hote
			
			windowTitle = "Disconnected from the server";
	        windowMessage = "The other player has been disconnected";
	        showErrorWindow = true;
			
			// deconnect Server
			Network.Disconnect();
	
	        //clean game fields
	        CleanServerVars();
	        this.GetComponent<GeneralManager>().gameStatus = GameStatus.Start;
			//hud.QuitAllScenes();
	        serverStatus = ServerStatus.Down;
		}
    }

    [RPC]
    public void GameSettingsToClient(string gameName)
    {
        //this.GetComponent<GeneralManager>().gameName = gameName;
        //this.GetComponent<GeneralManager>().gameStatus = GameStatus.Connected;
    }

    #endregion

    public void OnFailedToConnect(NetworkConnectionError error)
    {
        Debug.Log("Could not connect to server: " + error);
        windowTitle = "Failed to connect to server";
        string errorType = "";
        switch (error)
        {
            case NetworkConnectionError.InternalDirectConnectFailed:
                errorType = "-5 : Internal Direct Connect Failed";
                break;
            case NetworkConnectionError.EmptyConnectTarget:
                errorType = "-4 : Empty Connect Target";
                break;
            case NetworkConnectionError.IncorrectParameters:
                errorType = "-3 : Incorrect Parameters";
                break;
            case NetworkConnectionError.CreateSocketOrThreadFailure:
                errorType = "-2 : Create Socket Or Thread Failure";
                break;
            case NetworkConnectionError.AlreadyConnectedToAnotherServer:
                errorType = "-1 : Already Connected To Another Server";
                break;
            case NetworkConnectionError.NoError:
                errorType = "0 : ";
                break;
            case NetworkConnectionError.ConnectionFailed:
                errorType = "15 : Connection Failed";
                break;
            case NetworkConnectionError.TooManyConnectedPlayers:
                errorType = "18 : Too Many Connected Players";
                break;
            case NetworkConnectionError.RSAPublicKeyMismatch:
                errorType = "21 : RSA Public Key Mismatch";
                break;
            case NetworkConnectionError.ConnectionBanned:
                errorType = "22 : Connection Banned";
                break;
            case NetworkConnectionError.InvalidPassword:
                errorType = "23 : Invalid password";
                break;
            case NetworkConnectionError.NATTargetNotConnected:
                errorType = "69 : NAT Target Not Connected ";
                break;
            case NetworkConnectionError.NATTargetConnectionLost:
                errorType = "71 : NAT Target Connection Lost ";
                break;
            case NetworkConnectionError.NATPunchthroughFailed:
                errorType = "73 : NAT Punchthrough Failed ";
                break;
        }
        windowMessage = errorType;
        showErrorWindow = true;
    }

    public void OnFailedToConnectToMasterServer(NetworkConnectionError error)
    {
        Debug.Log("Could not connect to master server: " + error);
        windowTitle = "Failed to connect to MasterServer";
        string errorType = "";
        switch (error)
        {
            case NetworkConnectionError.InternalDirectConnectFailed:
                errorType = "-5 : Internal Direct Connect Failed";
                break;
            case NetworkConnectionError.EmptyConnectTarget:
                errorType = "-4 : Empty Connect Target";
                break;
            case NetworkConnectionError.IncorrectParameters:
                errorType = "-3 : Incorrect Parameters";
                break;
            case NetworkConnectionError.CreateSocketOrThreadFailure:
                errorType = "-2 : Create Socket Or Thread Failure";
                break;
            case NetworkConnectionError.AlreadyConnectedToAnotherServer:
                errorType = "-1 : Already Connected To Another Server";
                break;
            case NetworkConnectionError.NoError:
                errorType = "0 : ";
                break;
            case NetworkConnectionError.ConnectionFailed:
                errorType = "15 : Connection Failed";
                break;
            case NetworkConnectionError.TooManyConnectedPlayers:
                errorType = "18 : Too Many Connected Players";
                break;
            case NetworkConnectionError.RSAPublicKeyMismatch:
                errorType = "21 : RSA Public Key Mismatch";
                break;
            case NetworkConnectionError.ConnectionBanned:
                errorType = "22 : Connection Banned";
                break;
            case NetworkConnectionError.InvalidPassword:
                errorType = "23 : Invalid password";
                break;
            case NetworkConnectionError.NATTargetNotConnected:
                errorType = "69 : NAT Target Not Connected ";
                break;
            case NetworkConnectionError.NATTargetConnectionLost:
                errorType = "71 : NAT Target Connection Lost ";
                break;
            case NetworkConnectionError.NATPunchthroughFailed:
                errorType = "73 : NAT Punchthrough Failed ";
                break;
        }
        windowMessage = errorType;
        showErrorWindow = true;
    }

    public void ErrorWindow(int windowId)
    {
        GUILayout.Label(windowMessage);
        GUILayout.FlexibleSpace();
        GUILayout.BeginHorizontal();
        GUILayout.FlexibleSpace();
        if (GUILayout.Button("OK", GUILayout.MaxWidth(50)))
        {
            showErrorWindow = false;
        }
        GUILayout.FlexibleSpace();
        GUILayout.EndHorizontal();
    }
    //Error window
    public void DrawGUI()
    {
        errorWindow = new Rect(Screen.width / 2 - 125, Screen.height / 2 - 50, 250, 100);
        errorWindow = GUILayout.Window(0, errorWindow, ErrorWindow, windowTitle);
    }
	
	public void Update()
	{
		if (MasterServer.PollHostList().Length != 0) {
            hostData = MasterServer.PollHostList();
            int i = 0;
			Debug.Log("Master server byblus game list: " + hostData[i].gameName);
            while (i < hostData.Length) {
                Debug.Log("Game name: " + hostData[i].gameName);
                i++;
            }
            MasterServer.ClearHostList();
        }
	}
}